#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/param.h>

#include "shm.h"

#define MMAP_NAME "/posix_mmap"

typedef struct _SHM_BLOCK
{
    unsigned int offset;
    unsigned int block_size; // aligned block size
    pthread_mutex_t mutex;
} SHM_BLOCK;

typedef struct _SHM_HEADER
{
    unsigned int magic_num;   // see SHM_MAGIC_NUM
    unsigned int refcount;    // total used shm modules count
    unsigned int block_count; // total shm block count
    unsigned int total_size;  // total aligned size
    pthread_mutex_t mutex;
    SHM_BLOCK block[SHM_BLOCK_COUNT_MAX];
    unsigned char shm_data[0];
} SHM_HEADER;

static pthread_once_t g_shm_init_once = PTHREAD_ONCE_INIT;
static unsigned int s_shm_header_align_size = SHM_SIZE(sizeof(SHM_HEADER));

// clang-format off
/* the count of blocks should less than SHM_BLOCK_COUNT_MAX */
static SHM_BLOCK_INFO *g_shm_blocks = NULL;
static unsigned int g_shm_blocks_count = 0;
// clang-format on

SHM_BLOCK_INFO *get_shm_blocks(void)
{
    return g_shm_blocks;
}

unsigned int get_shm_blocks_count(void)
{
    return g_shm_blocks_count;
}

unsigned int get_shm_align_header_size(void)
{
    return s_shm_header_align_size;
}

unsigned int get_shm_real_block_size(unsigned int block_id)
{
    if (block_id >= g_shm_blocks_count)
    {
        /* block_id error */
        return SHM_BLOCK_SIZE_INVALID;
    }
    return (g_shm_blocks[block_id].block_size);
}

unsigned int get_shm_align_block_size(unsigned int block_id)
{
    if (block_id >= g_shm_blocks_count)
    {
        /* block_id error */
        return SHM_BLOCK_SIZE_INVALID;
    }
    return g_shm_blocks[block_id].align_block_size;
}

unsigned int get_shm_align_blocks_size(void)
{
    unsigned int total_align_size = 0;
    for (unsigned int i = 0; i < g_shm_blocks_count; i++)
    {
        total_align_size += g_shm_blocks[i].align_block_size;
    }
    return total_align_size;
}

unsigned int get_shm_align_total_size(void)
{
    return s_shm_header_align_size + get_shm_align_blocks_size();
}

/* share memory attach ptr */
static void *s_shmem = MAP_FAILED;

static int shm_header_init(SHM_HEADER *shm_header_ptr)
{
    unsigned int i = 0;
    unsigned int offset = 0; // mean begin of shm_header_ptr->shm_data 's offset
    pthread_mutexattr_t mutex_attr;
    int ret = -1;

    pthread_mutexattr_init(&mutex_attr);
    pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
    pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST);

    memset((void *)shm_header_ptr, 0, sizeof(SHM_HEADER));
    pthread_mutex_init(&(shm_header_ptr->mutex), &mutex_attr);

    int lock_err = pthread_mutex_lock(&(shm_header_ptr->mutex));
    if (lock_err == EOWNERDEAD)
    {
        ret = pthread_mutex_consistent(&(shm_header_ptr->mutex));
        if (0 != ret)
        {
            printf("pthread_mutex_consistent fail, ret[%d], errno[%d](%s)", ret, errno, strerror(errno));
            // 后续如果有其他进程再次尝试加锁收到ENOTRECOVERABLE错误码, 此时在这种情况下, 应该销毁并重新初始化互斥锁
        }
    }
    else if (lock_err != 0)
    {
        printf("pthread_mutex_lock fail, ret[%d], errno[%d](%s)", ret, errno, strerror(errno));
        return -1;
    }
    shm_header_ptr->magic_num = SHM_MAGIC_NUM;
    shm_header_ptr->refcount++;
    shm_header_ptr->block_count = get_shm_blocks_count();
    shm_header_ptr->total_size = get_shm_align_total_size();

    /* init blocks and create mutex among processes */
    for (i = 0; i < shm_header_ptr->block_count; i++)
    {
        pthread_mutex_init(&(shm_header_ptr->block[i].mutex), &mutex_attr);
        shm_header_ptr->block[i].offset = offset;
        shm_header_ptr->block[i].block_size = get_shm_align_block_size(i);
        memset(shm_header_ptr->shm_data + shm_header_ptr->block[i].offset, 0x00, shm_header_ptr->block[i].block_size);
        offset += shm_header_ptr->block[i].block_size;
    }
    pthread_mutex_unlock(&(shm_header_ptr->mutex));
    pthread_mutexattr_destroy(&mutex_attr);

    return 0;
}

#if 0
static int _shm_init()
{
    if (s_shmem != (void *)-1)
    {
        /* share memory already be created */
        return 0;
    }

    /* create a key */
    const char *filepath = "/";
    key_t key = ftok(filepath, 1);

    /* create share memory */
    unsigned int total_size = get_shm_align_total_size();
    int shmid = shmget(key, total_size, 0666 | IPC_CREAT);
    if (shmid == -1)
    {
        return -1;
    }

    /* attach share memory */
    s_shmem = shmat(shmid, 0, 0);
    if (s_shmem == (void *)-1)
    {
        return -1;
    }

    return 0;
}
#else
static int _shm_init(void)
{
    int ret = 0;

    if (s_shmem != MAP_FAILED)
    {
        /* share memory already be created */
        return 0;
    }

    // try connect
    size_t total_size = get_shm_align_total_size();
    int shmfd = shm_open(MMAP_NAME, O_RDWR, 0);
    if (shmfd == -1)
    {
        printf("shm_open try connect-(%s)", strerror(errno));
        // try create
        shmfd = shm_open(MMAP_NAME, O_RDWR | O_CREAT, 0666);
        if (shmfd == -1)
        {
            ret = -1;
            perror("shm_open(create) failed");
            goto shm_exit;
        }

        /* Set the size of shm */
        if (ftruncate(shmfd, (off_t)total_size) == -1)
        {
            ret = -1;
            perror("ftruncate failed");
            goto shm_exit;
        }
    }
    else
    {
        struct stat shm_stat;

        /* Get the size of shm */
        if (fstat(shmfd, &shm_stat) == -1)
        {
            ret = -1;
            perror("fstat() failed");
            goto shm_exit;
        }
        total_size = (size_t)shm_stat.st_size;
    }

    /* attach share memory */
    void *addr = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0);
    if (addr == MAP_FAILED)
    {
        ret = -1;
        perror("mmap() failed");
        goto shm_exit;
    }

shm_exit:
    /* The 'shmfd' is no longer needed */
    if (-1 != shmfd)
    {
        if (close(shmfd) == -1)
        {
            perror(
                "Failed to close shared memory"
                " file descriptor");
        }
    }

    if (ret == 0)
    {
        s_shmem = addr;
    }
    else
    {
        s_shmem = MAP_FAILED;
    }

    return ret;
}
#endif

static void _shm_init_once(void)
{
    int ret = _shm_init();
    printf("_shm_init ret[%d]", ret);
    if (0 != ret)
    {
        exit(-1);
    }
}

int shm_init_once(SHM_BLOCK_INFO *shm_blocks, unsigned int shm_blocks_count)
{
    g_shm_blocks = shm_blocks;
    g_shm_blocks_count = shm_blocks_count;

    pthread_once(&g_shm_init_once, _shm_init_once);

    /* convert to SHM_HEADER type */
    SHM_HEADER *shm_header_ptr = (SHM_HEADER *)s_shmem;
    if (SHM_MAGIC_NUM != shm_header_ptr->magic_num || 0 == shm_header_ptr->refcount)
    {
        // only the first module should goto here
        shm_header_init(shm_header_ptr);
    }

    return 0;
}

int shm_init(void)
{
    pthread_once(&g_shm_init_once, _shm_init_once);

    /* convert to SHM_HEADER type */
    int ret = -1;
    SHM_HEADER *shm_header_ptr = (SHM_HEADER *)s_shmem;
    if (SHM_MAGIC_NUM != shm_header_ptr->magic_num || 0 == shm_header_ptr->refcount)
    {
        printf("shm_init_once not handle!\n");
        return -1;
    }

    int lock_err = pthread_mutex_lock(&(shm_header_ptr->mutex));
    if (lock_err == EOWNERDEAD)
    {
        ret = pthread_mutex_consistent(&(shm_header_ptr->mutex));
        if (0 != ret)
        {
            printf("pthread_mutex_consistent fail, ret[%d], errno[%d](%s)", ret, errno, strerror(errno));
            // 后续如果有其他进程再次尝试加锁收到ENOTRECOVERABLE错误码, 此时在这种情况下, 应该销毁并重新初始化互斥锁
        }
    }
    else if (lock_err != 0)
    {
        printf("pthread_mutex_lock fail, ret[%d], errno[%d](%s)", ret, errno, strerror(errno));
        return -1;
    }
    shm_header_ptr->refcount++;
    pthread_mutex_unlock(&(shm_header_ptr->mutex));

    return 0;
}

int shm_lock(unsigned int block_id)
{
    if (s_shmem == MAP_FAILED)
    {
        return -1;
    }

    if (block_id >= get_shm_blocks_count())
    {
        return -1;
    }

    SHM_HEADER *shm_header_ptr = (SHM_HEADER *)s_shmem;
    int ret = -1;
    int lock_err = pthread_mutex_lock(&(shm_header_ptr->block[block_id].mutex));
    if (lock_err == EOWNERDEAD)
    {
        ret = pthread_mutex_consistent(&(shm_header_ptr->block[block_id].mutex));
        if (0 != ret)
        {
            printf("pthread_mutex_consistent fail, ret[%d], errno[%d](%s)", ret, errno, strerror(errno));
            // 后续如果有其他进程再次尝试加锁收到ENOTRECOVERABLE错误码, 此时在这种情况下, 应该销毁并重新初始化互斥锁
        }
    }
    else if (lock_err != 0)
    {
        printf("pthread_mutex_lock fail, ret[%d], errno[%d](%s)", ret, errno, strerror(errno));
        return -1;
    }

    return 0;
}

int shm_unlock(unsigned int block_id)
{
    if (s_shmem == MAP_FAILED)
    {
        return -1;
    }
    if (block_id >= get_shm_blocks_count())
    {
        return -1;
    }

    SHM_HEADER *shm_header_ptr = (SHM_HEADER *)s_shmem;
    pthread_mutex_unlock(&(shm_header_ptr->block[block_id].mutex));

    return 0;
}

void *shm_get_block_addr(unsigned int block_id)
{
    if (block_id >= get_shm_blocks_count())
    {
        return NULL;
    }

    SHM_HEADER *shm_header_ptr = (SHM_HEADER *)s_shmem;
    unsigned int offset = shm_header_ptr->block[block_id].offset;

    return (void *)(shm_header_ptr->shm_data + offset);
}

int shm_get_block_data(unsigned int block_id, unsigned int data_len, void *data_buf)
{
    if (block_id >= get_shm_blocks_count())
    {
        return -1;
    }

    int ret = shm_lock(block_id);
    if (0 != ret)
    {
        return ret;
    }

    unsigned int real_block_size = get_shm_real_block_size(block_id);
    if (SHM_BLOCK_SIZE_INVALID == real_block_size)
    {
        return -1;
    }
    unsigned int copy_len = MIN(data_len, real_block_size);
    SHM_HEADER *shm_header_ptr = (SHM_HEADER *)s_shmem;
    unsigned int offset = shm_header_ptr->block[block_id].offset;
    memcpy(data_buf, shm_header_ptr->shm_data + offset, copy_len);
    shm_unlock(block_id);

    return 0;
}

int shm_set_block_data(unsigned int block_id, unsigned int data_len, void *data_buf)
{
    if (block_id >= get_shm_blocks_count())
    {
        return -1;
    }
    unsigned int real_block_size = get_shm_real_block_size(block_id);
    if (SHM_BLOCK_SIZE_INVALID == real_block_size || data_len > real_block_size)
    {
        return -1;
    }

    int ret = shm_lock(block_id);
    if (0 != ret)
    {
        return ret;
    }

    SHM_HEADER *shm_header_ptr = (SHM_HEADER *)s_shmem;
    unsigned int offset = shm_header_ptr->block[block_id].offset;
    memset(shm_header_ptr->shm_data + offset, 0, shm_header_ptr->block[block_id].block_size);
    memcpy(shm_header_ptr->shm_data + offset, data_buf, data_len);
    shm_unlock(block_id);

    return 0;
}
